﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace DynaSpace.Menu
{
	/// <summary>
	/// Třída reprezentující seznam <see cref="MenuItem">položek menu</see>
	/// Stará se o jejich update, řízení focusu a vykreslování.
	/// </summary>
	public class MenuContainer
	{
		private List<MenuItem> items = new List<MenuItem>();
		private MenuItemUpdateData menuItemUpdateData = null;
		private Viewport viewport;
		private bool firstSetFocus = true;

		/// <summary>
		/// Konstruktor <see cref="MenuContainer"/>
		/// </summary>
		/// <param name="name">Identifikační jméno menu</param>
		/// <param name="visible">Počáteční viditelnost menu</param>
		public MenuContainer(string name,bool visible)
		{
			Viewport = new Viewport()
			{
				X = 0,
				Y = 0,
				Width = 350,
				Height = 600
			};
			MarginTop = 10;
			MarginLeft = 10;
			MarginRight = 10;
			MarginBottom = 10;
			Visible = visible;
			AutoHeight = true;
			Name = name;
		}

		/// <summary>
		/// Vrací seznam <see cref="MenuItem">položek menu</see>
		/// </summary>
		public List<MenuItem> Items
		{
			get
			{
				return items;
			}
		}

		/// <summary>
		/// Vrací velikost horniho okraje v pixelech
		/// </summary>
		public int MarginTop { get; protected set; }
		
		/// <summary>
		/// Vrací velikost levého okraje v pixelech
		/// </summary>
		public int MarginLeft { get; protected set; }
		
		/// <summary>
		/// Vrací velikost spodního okraje v pixelech
		/// </summary>
		public int MarginBottom { get; protected set; }

		/// <summary>
		/// Vrací velikost pravého okraje v pixelech
		/// </summary>
		public int MarginRight { get; protected set; }

		/// <summary>
		/// Vrací <see cref="MenuItemUpdateData">data o updatu</see>
		/// </summary>
		internal protected MenuItemUpdateData MenuItemUpdateData
		{
			get
			{
				if (menuItemUpdateData == null)
				{
					menuItemUpdateData = new MenuItemUpdateData()
					{
						Menu = this,
						ItemWithFocus = null
					};
				}
				return menuItemUpdateData;
			}
			protected set
			{
				menuItemUpdateData = value;
			}
		}

		/// <summary>
		/// Updatuje stavy prvků v menu
		/// </summary>
		/// <param name="gameTime">Herní čas</param>
		/// <param name="haveFocus">Pokud je true, bude se předpokládat, že jde menu ovládat</param>
		public void Update(GameTime gameTime, bool haveFocus)
		{
			// pokud se provádí první update, tak nastavíme focus na první ovládací prvek
			if (firstSetFocus)
			{
				SetFocusToFirstControl();
				firstSetFocus = false;
			}

			if (!Visible)
				return;

			// nastavíme, zda má menu focus
			this.HaveFocus = haveFocus;

			int moveFocus = 0;

			// pokud byl prvek smazán (vybranný neexistuje v kolekci prvků), bude vybrán první další
			if(MenuItemUpdateData.ItemWithFocus != null &&
				!items.Contains(MenuItemUpdateData.ItemWithFocus))
			{
				MenuItemUpdateData.ItemWithFocus = null;
				moveFocus = 1;
			}

			// presun na predchozi prvek
			if (haveFocus && DynaGame.Game.IsKeyPressedOnce(PlayerIndex.One, DynaSpace.Input.PlayerButton.Down))
			{
				moveFocus = 1;
			}

			// presun na dalsi prvek 
			if (haveFocus && DynaGame.Game.IsKeyPressedOnce(PlayerIndex.One, DynaSpace.Input.PlayerButton.Up))
			{
				moveFocus = -1;
			}

			// pokud objekt s focusem ztratil moznost mit focus, presuneme se na dalsi
			if (MenuItemUpdateData.ItemWithFocus != null &&
				MenuItemUpdateData.ItemWithFocus.CanFocus == false)
			{
				moveFocus = 1;
			}
			
			// budeme přesunovat focus?
			if (moveFocus != 0 && items.Count > 0)
			{
				// index vybraneho prvku (pokud neni zadny, tak -1)
				int startIndex = MenuItemUpdateData.ItemWithFocus == null ? -1 : items.IndexOf(MenuItemUpdateData.ItemWithFocus);

				int i = moveFocus;
				while (i <= items.Count  && i >= -items.Count)
				{
					int index = (i + startIndex) % items.Count;

					while (index < 0)
						index += items.Count;

					if (items[index].CanFocus) // muze mit nasledujici prvek focus?
					{
						// predame focus a ukoncime prochazeni
						MenuItemUpdateData.ItemWithFocus = items[index];
						break;
					}

					i += moveFocus; // posun o dalsi prvek
				}
			}

			// pokud je aktivni rezim automaticke vysky, tak ji nastavime
			SetHeightIfAutoHeight();

			// zjistit, zda může menu získat focus
			bool canHaveFocus = false;
			foreach (MenuItem item in items)
			{
				if (item.CanFocus)
				{
					canHaveFocus = true;
					break;
				}
			}

			if (CanHaveFocus != canHaveFocus)
				CanHaveFocus = canHaveFocus;

			// update všech podprvků
			items.ForEach(mi => mi.Update(gameTime, MenuItemUpdateData));
		}

		/// <summary>
		/// Vykreslí všechny viditelné položky v menu
		/// </summary>
		/// <param name="spriteBatch"></param>
		public void Draw(SpriteBatch spriteBatch)
		{
			if (!Visible)
				return;

			spriteBatch.GraphicsDevice.Viewport = viewport;

			spriteBatch.Begin();

			// vykreslit pozadí menu
			DrawBackground(spriteBatch);
			
			Vector2 position = new Vector2(MarginLeft,MarginTop);

			// projít všechy viditelné prvky
			foreach (MenuItem item in items.Where(mi=>mi.Visible == true))
			{
				item.Draw(spriteBatch, position, MenuItemUpdateData);

				position.Y += item.Height;
			}

			spriteBatch.End();
		}

		/// <summary>
		/// Vykreslí okraje a podklad menu
		/// </summary>
		/// <param name="spriteBatch">SpriteBatch pro vykreslování</param>
		protected virtual void DrawBackground(SpriteBatch spriteBatch)
		{
			Color color = Color.White;
			Texture2D tex = ContentComponent.panel1;

			// koordinaty na panelu
			int[] panX = new int[] { MarginLeft, Viewport.Width - MarginLeft - MarginRight, MarginRight };
			int[] panY = new int[] { MarginTop, Viewport.Height - MarginTop - MarginBottom, MarginBottom };
			int[] texX = new int[] { MarginLeft, tex.Width - MarginLeft - MarginRight, MarginRight };
			int[] texY = new int[] { MarginTop, tex.Height - MarginTop - MarginBottom, MarginBottom };

			// Roh: levý horní
			spriteBatch.Draw(tex, new Rectangle(0, 0, panX[0], panY[0]), new Rectangle(0, 0, texX[0], texY[0]), color);
			// Roh: horní
			spriteBatch.Draw(tex, new Rectangle(panX[0], 0, panX[1], panY[0]), new Rectangle(texX[0], 0, texX[1], texY[0]), color);
			// Roh: pravý horní
			spriteBatch.Draw(tex, new Rectangle(panX[0] + panX[1], 0, panX[2], panY[0]), new Rectangle(texX[0]+texX[1], 0, texX[2], texY[0]), color);
			
			// Roh: levý
			spriteBatch.Draw(tex, new Rectangle(0, panY[0], panX[0], panY[1]), new Rectangle(0, texY[0], texX[0], texY[1]), color);
			// Obsah
			spriteBatch.Draw(tex, new Rectangle(panX[0], panY[0], panX[1], panY[1]), new Rectangle(texX[0], texY[0], texX[1], texY[1]), color);
			// Roh: pravý
			spriteBatch.Draw(tex, new Rectangle(panX[0] + panX[1], panY[0], panX[2], panY[1]), new Rectangle(texX[0] + texX[1], texY[0], texX[2], texY[1]), color);
			
			// Roh: levý dolní
			spriteBatch.Draw(tex, new Rectangle(0, panY[0] + panY[1], panX[0], panY[2]), new Rectangle(0, texY[0] + texY[1], texX[0], texY[2]), color);
			// Roh: dolní
			spriteBatch.Draw(tex, new Rectangle(panX[0], panY[0] + panY[1], panX[1], panY[2]), new Rectangle(texX[0], texY[0] + texY[1], texX[1], texY[2]), color);
			// Roh: pravý dolní
			spriteBatch.Draw(tex, new Rectangle(panX[0] + panX[1], panY[0] + panY[1], panX[2], panY[2]), new Rectangle(texX[0] + texX[1], texY[0] + texY[1], texX[2], texY[2]), color);
						
		}

		/// <summary>
		/// Vrací nebo nastavuje jméno seznamu položek menu
		/// </summary>
		public string Name
		{
			get;
			protected set;
		}

		/// <summary>
		/// Vrací nebo nastavuje, zda je menu vidět
		/// </summary>
		public bool Visible
		{
			get;
			set;
		}

		/// <summary>
		/// Vrací, zda má menu focus
		/// </summary>
		public bool HaveFocus
		{
			get;
			private set;
		}

		/// <summary>
		/// Vrací, zda může mít prvek focus. Bude true, pokud alespoň jedna položka v menu může získat focus.
		/// Pokud ne, menu nemůže přebrat focus hlavní herní ploše.
		/// </summary>
		public bool CanHaveFocus
		{
			get;
			private set;
		}

		/// <summary>
		/// Vrací viewport menu
		/// </summary>
		public Viewport Viewport
		{
			get { return viewport; }
			protected set { viewport = value; }
		}

		/// <summary>
		/// Vrací nebo nastavuje pozici menu
		/// </summary>
		public int Left
		{
			get { return viewport.X; }
			set { this.viewport.X = value; }
		}

		/// <summary>
		/// Vrací nebo nastavuje pozici menu
		/// </summary>
		public int Top
		{
			get { return viewport.Y; }
			set { this.viewport.Y = value; }
		}

		/// <summary>
		/// Vrací nebo nastavuje šířku menu
		/// </summary>
		public int Width
		{
			get { return viewport.Width; }
			set { this.viewport.Width = value; }
		}

		/// <summary>
		/// Vrací nebo nastavuje výšku menu, pokud neni zapnutá funkce AutoHeight
		/// </summary>
		public int Height
		{
			get { return viewport.Height; }
			set { this.viewport.Height = value; }
		}

		/// <summary>
		/// Vrací nebo nastavuje, zda je výška ovládacího prvku automatická podle jeho obsahu
		/// </summary>
		public bool AutoHeight
		{
			get;
			set;
		}

		/// <summary>
		/// Vycentruje pozici menu na střed obrazovky
		/// </summary>
		public void Center()
		{
			SetHeightIfAutoHeight();
			Left = DynaGame.Game.GraphicsDevice.PresentationParameters.BackBufferWidth / 2 - Width / 2;
			Top = DynaGame.Game.GraphicsDevice.PresentationParameters.BackBufferHeight / 2 - Height / 2;
		}

		/// <summary>
		/// Vycentruje pozici menu do pravého pruhu
		/// </summary>
		/// <param name="offset">Odsazení od krajů</param>
		public void CenterRight(int offset)
		{
			SetHeightIfAutoHeight();
			Left = DynaGame.Game.GraphicsDevice.PresentationParameters.BackBufferWidth - Width - offset;
			Top = offset;
			Height = DynaGame.Game.GraphicsDevice.PresentationParameters.BackBufferHeight - offset * 2;
		}

		/// <summary>
		/// Vycentruje pozici menu do levého pruhu
		/// </summary>
		/// <param name="offset">Odsazení od krajů</param>
		public void CenterLeft(int offset)
		{
			SetHeightIfAutoHeight();
			Left = offset;
			Top = offset;
			Height = DynaGame.Game.GraphicsDevice.PresentationParameters.BackBufferHeight - offset * 2;
		}

		/// <summary>
		/// Přidá focus prvním ovládacímu prvku, který focus podporuje
		/// </summary>
		public void SetFocusToFirstControl()
		{
			foreach (MenuItem item in items)
			{
				if (item.CanFocus)
				{
					this.MenuItemUpdateData.ItemWithFocus = item;
					break;
				}
			}
		}

		private void SetHeightIfAutoHeight()
		{
			if(AutoHeight)
				Height = MarginTop + MarginBottom + (int)items.Where(mi => mi.Visible).Sum(mi => mi.Height);
		}
	}
}
